#include "MainApplication.h"

#include <cstdint>

#include "CubeMapSphereEffect.h"
#include "D3DErrorChecker.h"
#include "EffectManager.h"
#include "InputLayoutManager.h"
#include "SpheresEffect.h"
#include "MathUtils.h"
#include "TextureManager.h"

namespace D3D10
{
	MainApplication::MainApplication(const HINSTANCE hInstance)
		: D3DFramework(hInstance)
	{

	}
	
	MainApplication::~MainApplication()
	{
		if (mDevice)
			mDevice->ClearState();
	
        D3D10Effects::EffectManager::destroyAll();
        D3D10InputLayouts::InputLayoutManager::destroyAll();
    }
	
	void MainApplication::init()
	{
		D3DFramework::init();

        D3D10Effects::EffectManager::initAll(mDevice);
        D3D10InputLayouts::InputLayoutManager::initAll(mDevice);
        D3D10Utils::GetTextureManager().init(mDevice);
       
        mSky.init(mDevice, 5000.0f); 
        mSpheres.init(mDevice);
		initLights();
	}
	
	void MainApplication::onResize()
	{
		D3DFramework::onResize();
	
        // Update projection perspective matrix.
		const float aspect = static_cast<float> (mClientWidth) / static_cast<float> (mClientHeight);
        D3D10Utils::GetCamera().setLens(0.25f * DirectX::XM_PI, aspect, 0.5f, 1000.0f);
    }
	
	void MainApplication::updateScene(const float deltaTime)
	{
		D3DFramework::updateScene(deltaTime);

        updateCamera(deltaTime);
    }
	
	void MainApplication::drawScene()
	{
		D3DFramework::drawScene();
	
        restoreDefaultStates();
        
        // Set per frame constants.
        D3D10Effects::SpheresEffect * const spheresEffect = D3D10Effects::EffectManager::mSpheresEffect;
        spheresEffect->setEyePosition(mEyePosition);
        spheresEffect->setDirectionalLight(mDirectionalLight);

        D3D10Effects::CubeMapSphereEffect * const cubeMapSphereEffect = D3D10Effects::EffectManager::mCubeMapSphereEffect;
        cubeMapSphereEffect->setEyePosition(mEyePosition);
        cubeMapSphereEffect->setDirectionalLight(mDirectionalLight);

        mSpheres.draw();

        mSky.draw();
		
		mSwapChain->Present(0, 0);
	}

    LRESULT MainApplication::msgProc(unsigned __int32 msg, WPARAM wParam, LPARAM lParam)
    {
        POINT currentMousePosition;
        __int32 dx = 0;
        __int32 dy = 0;
        switch(msg)
        {
        case WM_LBUTTONDOWN:
            if (wParam & MK_LBUTTON)
            {
                SetCapture(mMainWindow);

                mOldMousePosition.x = LOWORD(lParam);
                mOldMousePosition.y = HIWORD(lParam);
            }

            return 0;

        case WM_LBUTTONUP:
            ReleaseCapture();
            return 0;

        case WM_MOUSEMOVE:
            if (wParam & MK_LBUTTON)
            {
                currentMousePosition.x = static_cast<__int32> (LOWORD(lParam)); 
                currentMousePosition.y = static_cast<__int32> (HIWORD(lParam)); 

                dx = currentMousePosition.x - mOldMousePosition.x;
                dy = currentMousePosition.y - mOldMousePosition.y;

                const float rotationFactor = 0.005f;
                D3D10Utils::GetCamera().pitch(dy * rotationFactor);
                D3D10Utils::GetCamera().yaw(dx * rotationFactor);

                mOldMousePosition = currentMousePosition;
            }

            return 0;
        }

        return D3DFramework::msgProc(msg, wParam, lParam);
    }

	void MainApplication::initLights()
	{		
        mDirectionalLight.mAmbientColor = DirectX::XMFLOAT4A(0.4f, 0.4f, 0.4f, 1.0f);
		mDirectionalLight.mDiffuseColor = DirectX::XMFLOAT4A(1.0f, 1.0f, 1.0f, 1.0f);
		mDirectionalLight.mSpecularColor = DirectX::XMFLOAT4A(1.0f, 1.0f, 1.0f, 1.0f);	
        mDirectionalLight.mDirection = DirectX::XMFLOAT3A(0.57735f, -0.57735f, 0.57735f);
	}

    void MainApplication::restoreDefaultStates()
    {
        // Restore rasterizer state.
        mDevice->RSSetState(0);

        // Restore depth stencil state
        mDevice->OMSetDepthStencilState(0, 0);

        // Restore blend state
        const float blendFactors[] = {0.0f, 0.0f, 0.0f, 0.0f};
        mDevice->OMSetBlendState(0, blendFactors, 0xffffffff);
    }

    void MainApplication::updateCamera(const float deltaTime)
    {
        // Update angles based on input to orbit camera around scene.
        const float displacementFactor = 30.0f;
        if(GetAsyncKeyState('A') & 0x8000)	
            D3D10Utils::GetCamera().strafe(-displacementFactor * deltaTime);

        if(GetAsyncKeyState('D') & 0x8000)	
            D3D10Utils::GetCamera().strafe(displacementFactor * deltaTime);

        if(GetAsyncKeyState('W') & 0x8000)	
            D3D10Utils::GetCamera().walk(displacementFactor * deltaTime);

        if(GetAsyncKeyState('S') & 0x8000)	
            D3D10Utils::GetCamera().walk(-displacementFactor * deltaTime);

        D3D10Utils::GetCamera().updateViewMatrix();
        mEyePosition = D3D10Utils::GetCamera().getPosition();
    }
}
